package bancosys.tec.services.implementor.execution;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.List;

import jmine.tec.di.metadata.DependencyMetaData;
import jmine.tec.di.scope.TypedScope;
import jmine.tec.di.type.TypeConvertionException;
import jmine.tec.di.validation.StaticValidator;
import jmine.tec.services.annotations.Output;
import bancosys.tec.services.implementor.descriptor.OutputNameExtractor;
import bancosys.tec.services.implementor.descriptor.ServiceExecutionProxy;

/**
 * Abstract implementation of {@link ServiceExecutionProxy}
 * 
 * @author takeshi
 */
public abstract class AbstractServiceExecutionProxy implements ServiceExecutionProxy {

    private final DependencyMetaData[] meta;

    private final OutputNameExtractor outputNameExtractor;

    /**
     * @param meta the {@link DependencyMetaData}
     * @param outName String
     * @param literal boolean
     */
    public AbstractServiceExecutionProxy(DependencyMetaData[] meta, String outName, boolean literal) {
        super();
        this.meta = meta;
        if (outName == null) {
            this.outputNameExtractor = NULL_EXTRACTOR;
        } else if (literal) {
            this.outputNameExtractor = new LiteralOutputExtractor(outName);
        } else {
            this.outputNameExtractor = new OutputExtractorImpl(outName);
        }
    }

    /**
     * @param method the method
     * @return String
     */
    public static String extractOutName(Method method) {
        Output output = method.getAnnotation(Output.class);
        if (output == null) {
            return null;
        } else {
            return output.propertyName();
        }
    }

    /**
     * @param method the method
     * @return String
     */
    public static boolean isLiteral(Method method) {
        Output output = method.getAnnotation(Output.class);
        if (output == null) {
            return false;
        } else {
            return output.literal();
        }
    }

    /**
     * @param properties the properties
     * @return {@link List}
     */
    public final List<String> validateExecutionDependencies(TypedScope properties) {
        return StaticValidator.validateSimple(properties, this.meta);
    }

    /**
     * {@inheritDoc}
     */
    public final String getColumnName() {
        return this.outputNameExtractor.getColumnName();
    }

    /**
     * {@inheritDoc}
     */
    public final DependencyMetaData[] getExecutionDependencies() {
        DependencyMetaData[] copy = new DependencyMetaData[this.meta.length];
        System.arraycopy(this.meta, 0, copy, 0, copy.length);
        return copy;
    }

    /**
     * @param scope the scope
     * @return String
     */
    public final String getOutputName(TypedScope scope) {
        return this.outputNameExtractor.getOutputName(scope);
    }

    /**
     * @param properties the {@link TypedScope}
     * @return Object[]
     * @throws TypeConvertionException e
     */
    protected final Object[] resolveObjects(TypedScope properties) throws TypeConvertionException {
        Object[] arguments = new Object[this.meta.length];
        for (int i = 0; i < arguments.length; i++) {
            arguments[i] = this.meta[i].resolveObjectWrap(properties);
        }
        return arguments;
    }

    /**
     * {@inheritDoc}
     * 
     * @see bancosys.tec.services.implementor.descriptor.OutputNameExtractor#isLiteral()
     */
    public boolean isLiteral() {
        return this.outputNameExtractor.isLiteral();
    }

    /**
     * @author takeshi
     */
    private abstract static class AbstractNameExtractor implements OutputNameExtractor, Serializable {

        private final String column;

        /**
         * @param column String
         */
        public AbstractNameExtractor(String column) {
            super();
            this.column = column;
        }

        /**
         * {@inheritDoc}
         */
        public String getColumnName() {
            return column;
        }

    }

    /**
     * @author takeshi
     */
    private static final class LiteralOutputExtractor extends AbstractNameExtractor {

        /**
         * @param column String
         */
        public LiteralOutputExtractor(String column) {
            super(column);
        }

        /**
         * {@inheritDoc}
         */
        public String getOutputName(TypedScope scope) {
            return getColumnName();
        }

        /**
         * {@inheritDoc}
         */
        public boolean isLiteral() {
            return true;
        }

    }

    /**
     * @author takeshi
     */
    private static final class OutputExtractorImpl extends AbstractNameExtractor {

        /**
         * @param column String
         */
        public OutputExtractorImpl(String column) {
            super(column);
        }

        /**
         * {@inheritDoc}
         */
        public String getOutputName(TypedScope scope) {
            return scope.get(getColumnName(), String.class);
        }

        /**
         * {@inheritDoc}
         */
        public boolean isLiteral() {
            return false;
        }

    }

    private static final OutputNameExtractor NULL_EXTRACTOR = new OutputNameExtractor() {

        public boolean isLiteral() {
            return false;
        }

        public String getOutputName(TypedScope scope) {
            return null;
        }

        public String getColumnName() {
            return null;
        }
    };

}